﻿// Shader-Based-Image-Processing (SBIP)
// http://code.google.com/p/sbip/
//
// Copyright © Frank Nagl, 2009-2010
// admin@franknagl.de
//
namespace SBIP.Filter
{
    using System;
    using System.Reflection;
    using System.Resources;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;

    /// <summary>
    /// Base class for  based image processing filter.
    /// </summary>
    /// <remarks>
    /// Provides a method to init and configure a shader for a specified
    /// image processing filter.
    /// </remarks>
    public abstract class BaseFilter
    {
        /// <summary>The fx effect file, located in project's resources file.</summary>
        private readonly string effectFile;
        VertexDeclaration vertexDecl;
        /// <summary>Pointer to actual texture.</summary>
        private Texture2D texture;
        /// <summary>The render quad.</summary>
        private Quad quad;
        /// <summary>Reference to XNA graphics device. Note: Only using it in PostInit().</summary>
        private GraphicsDevice graphics;
        private string technique;

        /// <summary>The specified shader effect.</summary>
        protected Effect Effect { get; set; }

        /// <summary>Actual texture.</summary>
        internal Texture2D Texture
        {
            set
            {
                if (Effect == null)
                    throw new ArgumentException("Texture" + InitMsg);
                texture = value;
                Effect.Parameters["ImageTexture"].SetValue(value);
            }
        }

        /// <summary>
        /// The technique of the  effect. If no technique was specified, 
        /// the first technique of effect' shader code is used.
        /// </summary>
        public string Technique
        {
            get { return technique; }
            set 
            { 
                technique = value;
                if (Effect == null)
                    throw new ArgumentException("Technique" + InitMsg);
                Effect.CurrentTechnique = Effect.Techniques[value];
            }
        }

        /// <summary>Message for ArgumentException, thrown by defining a 
        /// property before setting <see cref="Processor.Filter"/>.</summary>
        protected const string InitMsg = " can be defined primaly after setting property Processor.Filter.";

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseFilter"/> class.
        /// </summary>
        /// <param name="effectFile">The  effect file, located in project's resources file.</param>
        protected BaseFilter(string effectFile) 
        {
            this.effectFile = effectFile;
        }

        protected Effect GetExternalEffect(string externalEffectFile)
        {
            // Load the effect from Resources.resx file.
            ResourceManager resource =
                new ResourceManager("SBIP.Resource",
                Assembly.GetExecutingAssembly());
            string s = resource.GetString(externalEffectFile);
            CompiledEffect compiledEffect = Effect.CompileEffectFromSource(
                s,
                null,
                null,
                CompilerOptions.None,
                TargetPlatform.Windows);

            if (!compiledEffect.Success)
            {
                string error = String.Format(
                    "Could not compile effect: " + externalEffectFile + " \n{0}",
                    compiledEffect.ErrorsAndWarnings);
                throw new InvalidOperationException(error);
            }
            
            Effect e = new Effect(
                graphics,
                compiledEffect.GetEffectCode(),
                CompilerOptions.None,
                null);
            return e;
        }

        /// <summary>
        /// Renders the specified effect into a texture.
        /// </summary>
        /// <param name="pTexture">The external texture to process.</param>
        /// <param name="pEffect">The effect.</param>
        /// <param name="pTechnique">The technique to use.</param>
        /// <param name="format">The format of the RenderTarget (destination texture).</param>
        /// <returns>The rendered texture.</returns>
        protected Texture2D _RenderEffectToTexture(Texture2D pTexture, Effect pEffect, string pTechnique, SurfaceFormat format)
        {
            RTT rtt = new RTT(graphics);
            rtt.PrepareRTT(pTexture, format);
            Texture2D tex = rtt.Filter(pTexture, pEffect, pTechnique, false);
            return tex;
        }

        /// <summary>
        /// Renders the specified effect into a texture.
        /// </summary>
        /// <param name="pEffect">The effect.</param>
        /// <param name="pTechnique">The technique to use.</param>
        /// <returns>The rendered texture.</returns>
        protected Texture2D _RenderEffectToTexture(Effect pEffect, string pTechnique)
        {
            RTT rtt = new RTT(graphics);
            Texture2D tex = rtt.Filter(texture, pEffect, pTechnique, true);
            return tex;
        }

        /// <summary>
        /// Renders the specified effect into a texture.
        /// </summary>
        /// <param name="pTexture">The external texture to process.</param>
        /// <returns>The rendered texture.</returns>
        protected Texture2D _RenderEffectToTexture(Texture2D pTexture)
        {
            RTT rtt = new RTT(graphics);
            return rtt.Filter(pTexture, Effect, technique, true);
        }

        /// <summary>
        /// Inits the specified effect.
        /// </summary>
        /// <param name="tex">The original texture to process.</param>
        /// <param name="graphicsDevice">The XNA graphics device.</param>
        internal void Init(Texture2D tex, GraphicsDevice graphicsDevice)
        {
            graphics = graphicsDevice;
            vertexDecl = new VertexDeclaration(graphicsDevice,
                             VertexPositionNormalTexture.VertexElements);
            //if (quad == null)
            quad = new Quad(tex.Width, tex.Height, graphicsDevice);
            graphicsDevice.VertexDeclaration = vertexDecl;

            Vector2 delta = 
                new Vector2(1.0f / tex.Width, 1.0f / tex.Height);

            // Load the effect from Resources.resx file.
            ResourceManager resource =
                new ResourceManager("SBIP.Resource",
                Assembly.GetExecutingAssembly());
            string s = resource.GetString(effectFile);
            CompiledEffect compiledEffect = Effect.CompileEffectFromSource(
                s,
                null,
                null,
                CompilerOptions.None,
                TargetPlatform.Windows);

            if (!compiledEffect.Success)
            {
                string error = String.Format(
                    "Could not compile effect: " + effectFile + " \n{0}",
                    compiledEffect.ErrorsAndWarnings);
                throw new InvalidOperationException(error);
            }
            
            Effect = new Effect(
                graphicsDevice,
                compiledEffect.GetEffectCode(),
                CompilerOptions.None,
                null);

            technique = Effect.CurrentTechnique.Name;
            //effect.Parameters["ImageTexture"].SetValue(tex);
            Texture = tex;
            Effect.Parameters["Delta"].SetValue(delta);

            PostInit();
        }

        /// <summary>
        /// Provides inidvidual filter initialization after graphics device 
        /// and effect initialization.
        /// </summary>
        protected virtual void PostInit() { }

        /// <summary>Renders the specified effect.</summary>
        internal virtual void RenderEffect()
        {
            Effect.Begin();
            foreach (EffectPass pass in Effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphics.DrawUserIndexedPrimitives(
                    PrimitiveType.TriangleList,
                    quad.Vertices,
                    0,
                    4,
                    quad.Indexes,
                    0,
                    2);

                pass.End();
            }
            Effect.End();        
        }

        /// <summary>Renders the specified effect into a texture.</summary>
        /// <returns>The rendered texture.</returns>
        internal virtual Texture2D RenderEffectToTexture()
        {
            RTT rtt = new RTT(graphics);
            return rtt.Filter(texture, Effect, technique, true);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        internal virtual void Dispose() 
        {
            Effect.Dispose();
        }
    }
}
